// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: xguo

#include "coin2/exchange/deribit_base/api_order/fix/fix_trade_client.h"

#include <utility>

#include "coin2/base/base64.h"
#include "coin2/exchange/base/api_util/api_signature.h"
#include "coin2/exchange/base/api_util/auth_key.h"
#include "coin2/exchange/deribit_base/api_order/fix/SecurityList.h"
#include "coin2/exchange/deribit_base/api_order/fix/fix_fields.h"
#include "coin2/exchange/deribit_futures/symbology/product.h"
#include "coin2/exchange/deribit_options/symbology/product.h"
#include "quickfix/Application.h"
#include "quickfix/Session.h"
#include "quickfix/SessionSettings.h"
#include "quickfix/fix44/SecurityList.h"

namespace coin2::exchange::deribit::api_order::fix {

FixTradeClient::FixTradeClient(std::string_view auth_key_file, const MarketExchangeApi& mea)
    : FixTradeClientBase<FixTradeClient>(auth_key_file), mea_{mea} {
  SetSessionSetting(trade_client_config_);
  msg_builder_ = std::make_unique<FixMessageBuilder>(mea_);
}

void FixTradeClient::onCreate(const FIX::SessionID&) {
  // do nothing
}

void FixTradeClient::onLogon(const FIX::SessionID&) { IsLogon(true); }

void FixTradeClient::onLogout(const FIX::SessionID& session_id) { IsLogon(false); }

void FixTradeClient::toAdmin(FIX::Message& message, const FIX::SessionID& session_id) {
  FIX::MsgType msg_type;
  message.getHeader().getFieldIfSet(msg_type);
  if (msg_type == "A") {
    FillLogonMessage(message);
  }
}

void FixTradeClient::toApp(FIX::Message&, const FIX::SessionID&) EXCEPT(FIX::DoNotSend) {
  // do nothing
}

void FixTradeClient::fromAdmin(const FIX::Message& message, const FIX::SessionID& session_id)
    EXCEPT(FIX::FieldNotFound, FIX::IncorrectDataFormat, FIX::IncorrectTagValue, FIX::RejectLogon) {
  // ProcessIncomeMsg(message, session_id);
}
void FixTradeClient::fromApp(const FIX::Message& message, const FIX::SessionID& session_id) EXCEPT(
    FIX::FieldNotFound,
    FIX::IncorrectDataFormat,
    FIX::IncorrectTagValue,
    FIX::UnsupportedMessageType) {
  ProcessIncomeMsg(message, session_id);

  ParseFixSecurityList(message);
}

void FixTradeClient::FillLogonMessage(FIX::Message& message) {
  try {
    // 1. get setting
    FIX::SessionID session_id = message.getSessionID();
    const FIX::Dictionary* settings = getInitiator().getSessionSettings(session_id);
    if (settings == nullptr) {
      LOG(INFO) << "setting nullptr.";
      return;
    }

    std::string user_name;
    if (settings->has(ACCESS_KEY)) {
      user_name = settings->getString(ACCESS_KEY);
    }

    std::string secret_key;
    if (settings->has(SECRET_KEY)) {
      secret_key = settings->getString(SECRET_KEY);
    }

    bool cood = true;
    if (settings->has(CANCEL_ORDERS_ON_DISCONNECT)) {
      cood = (settings->getString(CANCEL_ORDERS_ON_DISCONNECT) == "Y");
    }

    // 2. set fields
    int64_t curr_ts = GetCurrentTimestamp();
    std::string nonce =
        impl::base64_encode(reinterpret_cast<const unsigned char*>(&curr_ts), sizeof(curr_ts));
    std::string raw_data = std::to_string(curr_ts) + "." + nonce;
    std::string password = ApiSignatureUtil::GetHashBySHA256(raw_data + secret_key);

    message.setField(FIX::HeartBtInt(60));
    message.setField(FIX::RawData(raw_data));
    message.setField(FIX::Username(user_name));
    message.setField(FIX::Password(password));
    message.setField(FIX::CancelOrdersOnDisconnect(cood));
  } catch (FIX::Exception& ex) {
    LOG(ERROR) << "exception on fill logon message: " << ex.what() << "\n";
  } catch (std::exception& ex) {
    LOG(ERROR) << "exception on fill logon message: " << ex.what() << "\n";
  }
}

void FixTradeClient::Start() {
  if (auto executor = GetExecutor()) {
    timer_ = std::make_unique<boost::asio::steady_timer>(*executor);
    timer_->expires_after(std::chrono::seconds(10));
    timer_->async_wait(std::bind(&FixTradeClient::OnTimer, this, std::placeholders::_1));
  }
  FixTradeClientBase<FixTradeClient>::StartBase();
}

void FixTradeClient::Stop() { FixTradeClientBase<FixTradeClient>::StopBase(); }

void FixTradeClient::OnTimer(const boost::system::error_code& ec) {
  int interval = 10;  // 10 seconds
  if (IsLogon()) {
    RequestTypePtr message = msg_builder_->GenerateSecurityListRequest();
    this->Send(std::move(message));
    interval = 900;  // 15 mins
  }
  timer_->expires_after(std::chrono::seconds(interval));
  timer_->async_wait(std::bind(&FixTradeClient::OnTimer, this, std::placeholders::_1));
}

bool FixTradeClient::IsAvailable() const {
  return FixTradeClientBase<FixTradeClient>::IsAvailable();
}

void FixTradeClient::SetExecutor(Executor* executor) {
  FixTradeClientBase<FixTradeClient>::SetExecutor(executor);
}

void FixTradeClient::SetProcessor(Processor* processor) {
  FixTradeClientBase<FixTradeClient>::SetProcessor(processor);
}

void FixTradeClient::AddSubscriber(SubscriberCB cb) {
  FixTradeClientBase<FixTradeClient>::AddSubscriberV2(cb);
}

void FixTradeClient::PlaceOrder(const OrderExecution& ord_exec) {
  CHECK(msg_builder_);
  auto contract_multiplier = GetContractValue(
      ord_exec.order_spec->product,
      ord_exec.order_spec->order_created_time,
      mea_,
      GetProductHolder());
  auto message =
      msg_builder_->GeneratePlaceOrder(ord_exec, GetProductHolder(), contract_multiplier);
  if (!message) {
    LOG(ERROR) << "failed to generate place order: " << ord_exec.proc_order_id;
    return;
  }
  this->Send(std::move(message));
}

void FixTradeClient::CancelOrder(const OrderExecution& ord_exec) {
  CHECK(msg_builder_);
  auto message = msg_builder_->GenerateCancelOrder(ord_exec);
  if (!message) {
    LOG(ERROR) << "failed to generate cancel order: " << ord_exec.proc_order_id;
    return;
  }
  this->Send(std::move(message));
}

void FixTradeClient::AssignSubSystemAuthKey(FIX::SessionSettings* session_setting) {
  auto auth_key = AuthKey::FromFile(GetAuthKey());
  ValidateSubSystemAuthKey(auth_key);
  std::string sender_comp_id = auth_key.access_key();

  FIX::Dictionary dict;
  dict.setString(ACCESS_KEY, auth_key.access_key());
  dict.setString(SECRET_KEY, auth_key.secret_key());
  FIX::SessionID session_id{begin_string_, sender_comp_id, target_comp_id_};
  session_setting->set(session_id, dict);
}

void FixTradeClient::ValidateSubSystemAuthKey(const AuthKey& auth_key) const {
  // TODO(xguo) - validate
}

auto FixTradeClient::GetContractValue(
    const std::string& native_product,
    int64_t ts,
    const MarketExchangeApi& mea,
    GetProductHolderFunc get_product_holder) -> double {
  if (contract_value_map_.count(native_product) == 0) {
    if (mea.market == coin::proto::Futures) {
      using coin2::exchange::deribit_futures::symbology::DeribitFuturesProduct;
      auto product = DeribitFuturesProduct::FromStr(native_product, ts);
      auto product_holder = get_product_holder(product, ts);
      // daniel(20230803): for Deribit futures, the contract value always is set as 1
      //                   and the qty ticksize is set as contract size when retrieve
      //                   pi from exchange
      contract_value_map_[native_product] = product_holder->product_info().tick_qty().TickSize();
    } else if (mea.market == coin::proto::Options) {
      using coin2::exchange::deribit_options::symbology::DeribitOptionsProduct;
      auto product = DeribitOptionsProduct::FromStr(native_product, ts);
      auto product_holder = get_product_holder(product, ts);
      contract_value_map_[native_product] = product_holder->product_info().contract_value();
    } else {
      CHECK(false) << "Unknown market type " << mea.String();
    }
  }
  return contract_value_map_.at(native_product);
}

void FixTradeClient::SetContractValue(const std::string& native_product, double val) {
  if (contract_value_map_[native_product] == val) {
    return;
  }
  SPDLOG_INFO(
      "reset contract multiplier for symbol {}: {} -> {}",
      native_product,
      contract_value_map_[native_product],
      val);
  contract_value_map_[native_product] = val;
}

void FixTradeClient::ParseFixSecurityList(const FIX::Message& message) {
  const std::string MSG_TYPE_SECURITY_LIST = "y";
  FIX::MsgType msg_type;
  if (!message.getHeader().getFieldIfSet(msg_type)) {
    return;
  }
  if (MSG_TYPE_SECURITY_LIST != msg_type) {
    return;
  }
  using coin2::exchange::deribit::api_order::fix::SecurityList;
  SecurityList security_list(message);
  FIX::SecurityRequestResult security_request_result;
  if (!security_list.getFieldIfSet(security_request_result)) {
    LOG(INFO) << "SecurityRequestResult not found!";
    return;
  }
  if (0 != security_request_result) {
    return;
  }
  FIX::NoRelatedSym no_related_sym_cnt;
  if (!security_list.getFieldIfSet(no_related_sym_cnt)) {
    return;
  }
  if (0 >= no_related_sym_cnt) {
    return;
  }

  for (int i = 1; i <= no_related_sym_cnt; ++i) {
    FIX44::SecurityList::NoRelatedSym no_related_sym;
    security_list.getGroup(i, no_related_sym);
    FIX::Symbol symbol;
    if (!no_related_sym.getFieldIfSet(symbol)) {
      continue;
    }
    FIX::ContractMultiplier contract_multiplier;
    if (!no_related_sym.getFieldIfSet(contract_multiplier)) {
      continue;
    }
    SetContractValue(symbol, contract_multiplier);
  }
}

}  // namespace coin2::exchange::deribit::api_order::fix
